<!--

/*#
# Copyright 2010 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This code is not supported by Google
#
*/
-->

<!--
Sample .jsp code for Google Commerce Search to submit ShoppingAPI-Search queries 
 then parse out the responses for display. 
 Shows additive filtering, page navigation and local Availability results.
 
 Local Availability is ONLY work if your GCS instance and API key has been
 whitelisted AND your site is *already* listed and enrolled on Google Product 
 Search Local Availability:
 http://www.google.com/intl/en_us/products/local.html
 
 On every page load, the site will prompt you to detect your location based
 on W3C geolocation API: http://en.wikipedia.org/wiki/W3C_Geolocation_API
 Normally, this is done only once and cached (or detected automatically on
 a mobile device).
 
 The detected lat+lng coordiates are entered into the form.  
 You can override the autodetected coordiates to search from by entering in
 a string location value (eg, san+francisco  los+angeles  chicago tokyo).
 
 The string value will override the detected coordinates.
 
 For the given search term, GCS will search for ONLY those products available
 in 3 stores withn a given raidus of the coordinates or the entered location
 string.
 
 That is, if you enter in q=widget  location=san francisco  raidus=20mi
 
 GCS will only show widgets found at stores within 20miles of san francisco.
 
 For each result, click the 'show/hide' button to display a map with the 
 store locations.  The marker will show a popup whith the Google Places
 link for that store. 
 
 Documentation on local availability.
 https://code.google.com/apis/commercesearch/docs/local/gcs_local.html

 If you site has Search As You type enabled, this code will automatically show
 the suggestions.
 
 >>> This code is NOT designed for production deployment and has not been 
 throughly tested.  It should only be used to understand GCS features.  <<<


 Usage:
  
  Edit the file and specify the GCS instance ID (cx) and the API key you're 
  using.
  
  Deploy on any Servlet engine (e.g. Apache Tomcat)
  
 Requires Apache Commons Codec:
 http://commons.apache.org/codec/
   (copy commons-codec-*.jar inside WEB-INF/lib)

Deploy on any servlet-engine (eg Tomcat) and specify in your GCS cxnumber, 
API key and merchantID
https://code.google.com/apis/console/
http://www.google.com/cse/commercesearch/manage

--->

<%@ page import='java.util.*' %>
<%@ page import='java.io.*' %>
<%@ page import='java.net.*' %>
<%@ page import='javax.xml.parsers.*' %>
<%@ page import='org.w3c.dom.*' %>
<%@ page import='org.xml.sax.*' %>
<%@ page import='java.security.*' %>
<%@ page import='java.math.*' %>
<%@ page import='java.util.regex.*' %>
<%@ page import='javax.xml.xpath.*' %>
<%@ page import='javax.xml.namespace.*' %>
<%@ page import='javax.xml.parsers.*' %>
<%@ page import='org.apache.commons.codec.binary.Base64' %>

<%@ page session="true" %>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<%
//Edit the following and enter in your API KEY and CX number
String devkey = "YOUR KEY";
String cx = "YOUR CX";  
String country = "us";
String language = "en";

boolean local_enabled = true;
String location = "san+francisco";
String raidus = "20mi";
%>

<html>
    <head>
    <title>GCS Search</title>

<!-- FOR SAYT -->   
<link rel="stylesheet" href="http://www.google.com/cse/style/commerce_sayt.css" type="text/css" media="screen" />
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script src="https://apis.google.com/js/client.js" type="text/javascript"></script>
<script src="http://www.google.com/cse/intl/en-US/sayt.js" type="text/javascript"></script>

<!--  FOR LOCAL -->   	
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> 

<!-- FOR UI -->  
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <link rel="stylesheet" type="text/css" href="https://developers.google.com/css/screen.css" />
      <link href='//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700' rel='stylesheet' type='text/css'>    

	<style type="text/css">
	body.docs table {
	    border-collapse: collapse !important; 
	    border: 0px solid #BBBBBB  !important;
	    border-spacing: 0  !important;
	    margin: 0 0 0em  !important;
	}
	</style>         
	
	
	<script type="text/javascript">

    //for displaying local results an the map
	var map = null;
	var infowindow = null;
	
	  // Google Analytics integration
	  // Enter you analytics ID here
	  var _gaq = _gaq || [];
	  _gaq.push(['_setAccount', 'YOUR ANALYTICS ID']);
	  _gaq.push(['_trackPageview']);
	
	  (function() {
	    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
	    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
	    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
	  })();
	</script>


	<script language="javascript"> 

	function addhandler(marker, txt) {
		  google.maps.event.addListener(marker, 'click', function() { 
		     if (infowindow) infowindow.close();
		         infowindow = new google.maps.InfoWindow({content: txt});
		      infowindow.open(map,marker);
		  });
		}	

	function displayMap(div_id,stores_to_display) 
	{
     var bounds = new google.maps.LatLngBounds ();       
     var myOptions = { 
 	        mapTypeId: google.maps.MapTypeId.ROADMAP,
	        panControlOptions: {
	                            position: google.maps.ControlPosition.LEFT_BOTTOM
	                           }
	     };

	    map = new google.maps.Map(document.getElementById(div_id), myOptions);
		for (var item in stores_to_display ) 
		{		 
		 lat = stores_to_display[item][0];
		 lng = stores_to_display[item][1];
		 txt = "<a href='http://maps.google.com/maps/place?cid=" + stores_to_display[item][3] + "' target='_newwin'>" + stores_to_display[item][2] + "</a><br/>" + stores_to_display[item][4];			         
		 var s1 = new google.maps.LatLng(parseFloat(lat),parseFloat(lng));
         bounds.extend(s1); 
         map.fitBounds(bounds);
		 var marker = new google.maps.Marker({
		      position: s1,
		      title: txt,
		      map: map
		  });
	       addhandler(marker,txt);
	    }
	}
		
	//from http://blog.movalog.com/a/javascript-toggle-visibility/
	// some script to toggle the visibility of the per-item details
	var state = 'none';
	function showhide(id,productID) {
	var e = document.getElementById(id);
	if(e.style.display == 'block')
		e.style.display = 'none';
	else
		e.style.display = 'block';
	sendProductID(productID);
	}

	 //used to simulate sending google search requestID and item click
	 //simulation...normally this is done only when someone clicks on
	 //a search result	
	function sendProductID(productID)
	{
		_gaq.push(['_setCustomVar', 4, 'GCS_ProductID', productID, 3]);
		_gaq.push(['_trackPageview']);
	}
	function sendRequestID(requestID)
	{
		_gaq.push(['_setCustomVar', 3, 'GCS_RequestID', requestID, 3]);
		_gaq.push(['_trackPageview']);
	}	

	function initialize()  
	{
		<%
		if (local_enabled)
			out.write ("initializeGeoLocation();");
		%>
	}

	function initializeGeoLocation()
	{
		// geo location 
		if (navigator.geolocation) {
		    navigator.geolocation.getCurrentPosition(successFunction, errorFunction);
		}
	}
	
	function successFunction(position) {
	    var lat = position.coords.latitude;
	    var lng = position.coords.longitude;
	    
		if (lng>0 && lng<10)
			lng =  '0' + lng;
		if  (lng >=10 && lng <99)
			lng = '00' +  lng;	  
		if (lng<=-10 && lng>-99)			
			lng = '-0' + -1*lng;
		if (lng<=0 && lng>-10)
			lng = '-00' + -1*lng;
		
	    if (lat>0)
		    lat = '+' + lat;
	    if (lng>0)
		    lng = '+' + lng;
	    	    
	    var e = document.getElementById("lat");
	    e.value=lat;

	    var f = document.getElementById("lng");
	    f.value=lng; 
	}

	function errorFunction(position) {
		alert("unable to autodetect location");
	}
	</script>
	
    </head>

<%   
   String outbound_shopping = "";
   String requestID= "";
   String remove_filters = "";
   String totalResults = "";
   String page_nav = "";
   String html_attributes = "";
   String html_results = "";
   String coded_restrict = "";

   String q = request.getParameter("q");
   String startIndex = request.getParameter("startIndex");
   String restrictby = request.getParameter("restrictBy");
   String orderby = request.getParameter("orderBy");
   String sortorder = request.getParameter("sortOrder");
   long time_taken = 0;
   
   //the number of page links to show in the search result
   int TOTAL_PAGES_TO_SHOW = 20;
   
   // arraylist holding store information
   ArrayList<LocalStore> al_stores = new ArrayList<LocalStore>();   
   
    // creates an array of all request GET parameters
   	Iterator ii = request.getParameterMap().keySet().iterator();
	HashMap<String, String> kvpairs = new HashMap<String,String>();
	while ( ii.hasNext() )
	{
		String pkey = (String) ii.next();
		String pvalue = ((String[]) request.getParameterMap().get( pkey ))[ 0 ];
		kvpairs.put(pkey,pvalue);
	}

	 // now figure out the location information to search from
	   String loc_string = "";
	   if (local_enabled)
	   {
		 if (request.getParameter("location")!=null)
			 if (!request.getParameter("location").equals(""))
			 {
			   location = URLEncoder.encode(new String(request.getParameter("location").getBytes("ISO-8859-1"),"UTF-8"));
			 }
			 else 
			 {
				 String slat = request.getParameter("lat");
				 String slng = request.getParameter("lng");
				 if (slat!=null && slng!=null)
					 if (!slat.equals("") && !slng.equals(""))
						 location = "(" + URLEncoder.encode(slat) + "" + URLEncoder.encode(slng) + "/)";
			 }
		 if (request.getParameter("raidus")!=null)
			 if (!request.getParameter("raidus").equals(""))
			 {   // make sure the 'raidus' is a number
				 raidus = request.getParameter("raidus");	
				 if (!raidus.matches("-?\\d+(.\\d+)?"))
					raidus = "20mi";
				 else
					 raidus += request.getParameter("dunit");	
				 
			 }
	     loc_string = "&channels=local:3:inRange:" + location + ":" + raidus;
	   }	
	
   String userIP = request.getRemoteAddr();   
   // the outbound query to send to GCS
   String shopping_base_query = "https://www.googleapis.com/shopping/search/v1/cx:" + cx + "/products?facets.enabled=true&country=" + country + "&maxResults=10&facets.useGcsConfig=true&facets.discover=20:10&alt=atom&rankBy=relevancy&userIP=" + userIP;

   // if not specified, initialize the variables for the call
   
   // escape the URL query string for languages
   if (q != null)
     q = "&q=" + URLEncoder.encode(new String(q.getBytes("ISO-8859-1"), "UTF-8"));
   else
     q="";


   if (startIndex != null)
     startIndex = "&startIndex=" + startIndex;
   else
     startIndex = "";

   if (orderby != "")
   {
     orderby = "";
     sortorder = "";
   }
   
   // now decode and then separate out each restrictBy= combined parameter
   // i.e, base64 decode first, then parse out the restrictBy= parameter 
   // into separate components
	   coded_restrict = request.getParameter("restrictBy");
	   if (coded_restrict == null)
		   coded_restrict = "";
	   Vector<String> vrestricts = getRestricts(decode(coded_restrict));

   // for each restrict, reconstruct the query string to send to GCS
   if (restrictby != null)
   {
	   String tmp_restrict = "";
	   for (String s : vrestricts)
	   {
			   String cat = s.split("=")[0];
			   String val = s.split("=")[1];
			   tmp_restrict += cat.replace(" ","+") + "=" + URLEncoder.encode(this.escapeAttribute(cat,val)) +",";
		
	   }
	   // if all we have is param1=value1,param2=value,  remove the last comma
	   if (tmp_restrict.endsWith(","))
		   tmp_restrict = tmp_restrict.substring(0,tmp_restrict.length()-1);
	   
	 if (tmp_restrict.equals(""))
	   restrictby= "";
	 else
	   restrictby= "&restrictBy=" + tmp_restrict;
   }
   else
	restrictby = "";

   // now send the query to GCS
   outbound_shopping = shopping_base_query + "&key=" + devkey + (startIndex + q  + orderby + sortorder + restrictby  + loc_string);
   
   System.out.println(outbound_shopping);
   
	    URL url = new URL(outbound_shopping);
    	Date starttime = new Date();
	    HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
	    InputStream inputStream = httpConnection.getInputStream();
	    Date endtime = new Date();
	    time_taken = endtime.getTime() - starttime.getTime();

	    DocumentBuilderFactory docBF = DocumentBuilderFactory.newInstance();
	    DocumentBuilder docBuilder = (DocumentBuilder) docBF.newDocumentBuilder();
	    Document doc = docBuilder.parse(inputStream);
	    doc.getDocumentElement().normalize();

	    // initialize the namespace to parse
		NamespaceContext ctx = new NamespaceContext()
		{
			public String getNamespaceURI(String prefix) {
				String uri;
				if (prefix.equals("openSearch"))
					uri = "http://a9.com/-/spec/opensearchrss/1.0/";
				else if (prefix.equals("s"))
					uri = "http://www.google.com/shopping/api/schemas/2010";
				else if (prefix.equals("gd"))
					uri = "http://schemas.google.com/g/2005";
				else if (prefix.equals("atom"))
					uri = "http://www.w3.org/2005/Atom";
				else
					uri = null;
				return uri;
			}
			public Iterator getPrefixes(String val) {
				return null;
			}         
			public String getPrefix(String uri) {
				return null;
			}
		};

        XPathFactory xpathFactory = XPathFactory.newInstance();
	    XPath xpath = xpathFactory.newXPath();
	    xpath.setNamespaceContext(ctx);

	    // first get the number of results
	    Node n_results = (Node) xpath.evaluate("/feed/totalResults", doc, XPathConstants.NODE);
	    if (n_results != null)
	       	totalResults =  n_results.getTextContent();
	    
	    // this is the unique per-request ID number.  This token is unique to
	    // each GCS call and should be 'sent back to GCS' through Google
	    // Analytics integration.  
	    // when someone searches for an item, the gcs response will contain
	    // a requestId.  Once the user 'clicks on an item' in the result set,
	    // you should send the requestID and the ProductID back via google
	    // analytics (sendAnalytics() function)
	    Node n_requestID = (Node) xpath.evaluate("/feed/requestId", doc, XPathConstants.NODE);
	    if (n_requestID != null)
	       	requestID =  n_requestID.getTextContent();
	    
	    
         //to parse spelling suggestions (not implemented)
        Node n_spelling = (Node) xpath.evaluate("/feed/spelling/suggestion", doc, XPathConstants.NODE);

   		//to parse promotions (not implemented)
        NodeList nl_promotions = (NodeList) xpath.evaluate("/feed/promotions/promotion", doc, XPathConstants.NODESET);

	    // now parse out the results and facets	   		
	        ArrayList<Entry> lst_entry = new ArrayList<Entry>();
	        ArrayList<Facet> lst_facet = new ArrayList<Facet>();
	    
	    // for each facet
	        NodeList nl_product = (NodeList) xpath.evaluate("/feed/entry/product", doc, XPathConstants.NODESET);
	        if (nl_product.getLength() > 0)
	        {
	        	for (int i=0; i< nl_product.getLength(); i++)
	        	{
	        		Node n_entry = nl_product.item(i);
	        		NodeList nl_entry_nodes = n_entry.getChildNodes();
	        		Entry e = new Entry();
	        		for (int j=0; j<nl_entry_nodes.getLength(); j++)
	        		{
	        			Node n_attribute = nl_entry_nodes.item(j);
	        			if (n_attribute.getNodeName().equals(Entry.GOOGLE_ID))
	        				e.addAttribute(Entry.GOOGLE_ID, Entry.TYPE_TEXT, n_attribute.getTextContent());
	        			else if (n_attribute.getNodeName().equals(Entry.PROVIDER_ID))
	        				e.addAttribute(Entry.PROVIDER_ID, Entry.TYPE_TEXT, n_attribute.getTextContent());
	        			else if (n_attribute.getNodeName().equals(Entry.TITLE))
	        				e.addAttribute(Entry.TITLE, Entry.TYPE_TEXT, n_attribute.getTextContent());
	        			else if (n_attribute.getNodeName().equals(Entry.LINK))
	        				e.addAttribute(Entry.LINK, Entry.TYPE_TEXT, n_attribute.getTextContent());
	        			else if (n_attribute.getNodeName().equals(Entry.DESC))
	        				e.addAttribute(Entry.DESC, Entry.TYPE_TEXT, n_attribute.getTextContent());
	        			else if (n_attribute.getNodeName().equals("s:inventories"))
	        			{
	        				//now for each inventory type (online/local, etc) save it for later use
	        				// you can have many different 'inventory' types and channels but
	        				// for display on your site, we usually pick the 'online'
	        				NodeList nl_inventories =(NodeList) xpath.evaluate("*", n_attribute, XPathConstants.NODESET);
							String online_price = "";
	        				for (int inv=0; inv<nl_inventories.getLength(); inv++)
	        				{
		        				Node ninventory = (Node) nl_inventories.item(inv);
								String channel = ninventory.getAttributes().getNamedItem("channel").getNodeValue();
								String availability = ninventory.getAttributes().getNamedItem("availability").getNodeValue();
								Node nprice = (Node)xpath.evaluate("price", ninventory, XPathConstants.NODE);															
								String currency = nprice.getAttributes().getNamedItem("currency").getNodeValue();
								String price = nprice.getTextContent();
								
								Inventory in = null;		

								if (channel.equals("online"))
								{
									in = new Inventory(channel,availability, currency,price);
									online_price = price;
								}								
								if (channel.equals("local"))
								{
									String storeId = ninventory.getAttributes().getNamedItem("storeId").getNodeValue();
									Node ndistance = (Node)xpath.evaluate("distance", ninventory, XPathConstants.NODE);									
									String unit = ndistance.getAttributes().getNamedItem("unit").getNodeValue();
									String distance = ndistance.getTextContent();									
									in = new Inventory(channel,availability, currency,price,storeId,unit,distance);
								}
								e.addInventory(in);
								//add the online price in
								e.addAttribute(Entry.PRICE, Entry.TYPE_TEXT, online_price);	        						        					
	        				}
	        			}
	        			else if (n_attribute.getNodeName().equals("s:images"))
	        			{
						Node image = (Node) xpath.evaluate("*", n_attribute, XPathConstants.NODE);
	        				e.addAttribute(Entry.IMAGE_LINK, Entry.TYPE_TEXT, image.getAttributes().getNamedItem("link").getNodeValue());
	        			}
	        			else if (n_attribute.getNodeName().equals("s:attributes"))
	        			{
	        				NodeList nl_facets = (NodeList) xpath.evaluate("*", n_attribute, XPathConstants.NODESET); 
	        				for (int ql=0; ql<nl_facets.getLength();ql++)
	        				{
	        					Node facet = nl_facets.item(ql);
	        					String facet_name = facet.getAttributes().getNamedItem("name").getNodeValue();
	        					String facet_type = facet.getAttributes().getNamedItem("type").getNodeValue();
	        					String facet_value = facet.getTextContent();	
	        					e.addAttribute(facet_name, facet_type,facet_value);
	        				}	
	        			}

	        		}
	        		lst_entry.add(e);
	        	}

	        	// now parse out the facets	        	
		        NodeList nl_facet = (NodeList) xpath.evaluate("/feed/facets/facet", doc, XPathConstants.NODESET);
		        if (nl_facet.getLength() > 0)
		        {	
				for (int i=0; i< nl_facet.getLength(); i++)
				{
					Node n_facet = nl_facet.item(i);
					Facet f = null;
					// Facets can be either standard properties or attributes.
					// see: http://code.google.com/apis/shopping/search/v1/reference-request-parameters.html#identifiers

					boolean is_property = false;
					
					if (n_facet.getAttributes().getNamedItem("property") != null)
						is_property = true;
					else
						is_property = this.IsProperty(n_facet.getAttributes().getNamedItem("name").getNodeValue());
							
				 	if (is_property == false)
					{
				 		String name = n_facet.getAttributes().getNamedItem("name").getNodeValue();						
						String type = n_facet.getAttributes().getNamedItem("type").getNodeValue();
						String count = n_facet.getAttributes().getNamedItem("count").getNodeValue();
						String unit = null;
						if (n_facet.getAttributes().getNamedItem("unit") != null)
							unit = n_facet.getAttributes().getNamedItem("unit").getNodeValue();
						f = new   Facet(name, type, count, unit);
						f.is_property = false;
				 	}
				 	else
				 	{
				 		String name = "";
				 		if (n_facet.getAttributes().getNamedItem("property") != null)
				 			name = n_facet.getAttributes().getNamedItem("property").getNodeValue();
				 		else
				 			name = n_facet.getAttributes().getNamedItem("name").getNodeValue();
				 		
						String count = n_facet.getAttributes().getNamedItem("count").getNodeValue();
				 		f = new Facet(name, null, count, null);
				 		f.is_property = true;
				 	}

					// now get the facet values out
					// again each facet value could either be a HISTOGRAM
					// or straight key-value pairs
					NodeList nl_buckets = (NodeList) xpath.evaluate("*", n_facet, XPathConstants.NODESET); 
					for (int j=0; j<nl_buckets.getLength(); j++)
					{
						Node n_bucket = nl_buckets.item(j);
						if (n_bucket.getAttributes().getNamedItem("value") != null)
						{
							// if key-values
							String value = null;
							String count = null;
							value = n_bucket.getAttributes().getNamedItem("value").getNodeValue();
							count = n_bucket.getAttributes().getNamedItem("count").getNodeValue();
							f.addDiscreteBucket(value,count);
						}
						else
						{
							// if HISTOGRAM
							String max = null;
							String min = null;
							String minExclusive = null;
							String maxExclusive = null;
							String count = null;
							if (n_bucket.getAttributes().getNamedItem("max") != null)
								max = n_bucket.getAttributes().getNamedItem("max").getNodeValue();
							if (n_bucket.getAttributes().getNamedItem("min") != null)
								min = n_bucket.getAttributes().getNamedItem("min").getNodeValue();
							if (n_bucket.getAttributes().getNamedItem("maxExclusive") != null)
								maxExclusive = n_bucket.getAttributes().getNamedItem("maxExclusive").getNodeValue();
							if (n_bucket.getAttributes().getNamedItem("minExclusive") != null)
								minExclusive = n_bucket.getAttributes().getNamedItem("minExclusive").getNodeValue();
							if (n_bucket.getAttributes().getNamedItem("count") != null)
								count = n_bucket.getAttributes().getNamedItem("count").getNodeValue();
							f.addHistogramBucket(min, max, minExclusive, maxExclusive, count);
						}
					}
					lst_facet.add(f);
				}
		        }	     	
	        }
	        
	    // parse out the store information if provided
	        NodeList nl_store = (NodeList) xpath.evaluate("/feed/stores/store", doc, XPathConstants.NODESET);
	        if (nl_store.getLength() > 0)
	        {	
				for (int i=0; i< nl_store.getLength(); i++)
				 {
					Node n_store = nl_store.item(i);
					LocalStore ls = null;
  				    String name = n_store.getAttributes().getNamedItem("name").getNodeValue();
  				    String storeId = n_store.getAttributes().getNamedItem("storeId").getNodeValue();
  				    String storeCode = n_store.getAttributes().getNamedItem("storeCode").getNodeValue();  				   
  				    String loc = n_store.getAttributes().getNamedItem("location").getNodeValue();
  				    String address = n_store.getAttributes().getNamedItem("address").getNodeValue();
  				    String telephone = n_store.getAttributes().getNamedItem("telephone").getNodeValue();
  				    ls = new LocalStore( storeId,  storeCode,  name,  loc,  address,  telephone);
				 	al_stores.add(ls);
				 }
			}
	    
	    // now for the complicated part:
	    // we need to carefully render the page
	    
	    // first get the original query string we got from the client
		String qstr = request.getParameter("q");
		if (qstr != null)
			qstr = "&q=" + qstr;
		else
			qstr = "";
		
		// Create the 'remove filters' section where if the user
		// typed in a query string, it'll show up as a filter that you can
		// remove on the rendered page.
		// basically what this means is you will constrict a link
		// that has every parameter *except* the query param
		String filter_q = request.getParameter("q");
		if (filter_q != null)
			remove_filters = "<a href='local.jsp?"  + constructQueryWithoutParameter(kvpairs,"q") + "'>" + new String(filter_q.getBytes("ISO-8859-1"), "UTF-8") + "</a>&nbsp;&nbsp;&nbsp;";

		// now get every other parameter except the restrictBy
		// because we need to do some special parsing on.
		String q_without_restrict = constructQueryWithoutParameter(kvpairs,"restrictBy");
	
		// now for every key=value entered INSIDE the &restrictBy, we need
		// recreate the query with every param *EXCEPT* the current one.
		// that is if the inboud query is restrictBy=key1=value1,key2=value2,key3=value3
		// we'll end up with three links:  
		// first one with a restrict only on key2=value2,key3=value3
		// first one with a restrict only on key1=value1,key3=value3
		// first one with a restrict only on key1=value1,key2=value2
		
		// NOTE instead of separating out the different restrictBy condidtions
		// with a ',' like it is in the SHoppingAPI, I just replaced it with 
		// a ^ to simplify the parsing (because a comma also exists inside
		// range restricts  
		for (String s: vrestricts)
		{
			String excluded_restrict = "";
			for (String r: vrestricts)
				if (!r.equals(s))
					excluded_restrict = excluded_restrict + r + "^";
			// again remove the trailing delimiter ^
			if (excluded_restrict.endsWith("^"))
				excluded_restrict = excluded_restrict.substring(0,excluded_restrict.length()-1);
			excluded_restrict = encode(excluded_restrict);
			// now render the 'remove filters link' item by item
			if (excluded_restrict.equals(""))
				remove_filters += "<a href='local.jsp?" + q_without_restrict + "'>" + s + "</a>&nbsp;&nbsp&nbsp";
			else
				remove_filters += "<a href='local.jsp?" + q_without_restrict + "&restrictBy=" + excluded_restrict + "'>" + s + "</a>&nbsp;&nbsp&nbsp";
		}

        //now pagenav page_nav
		int aInt = Integer.parseInt(totalResults);
		
		//then calculate the number of pages to show if there are 10 items per page.
		int num_pages = (int)Math.ceil(aInt/10);
		
		// display the page navigation
		// again each link in the page navigation uses the querystring
		// to add on all the restricts that was sent in with the previous request
		int start_index=1;
		for (int jj=0; jj<num_pages; jj++) {
			if (jj==TOTAL_PAGES_TO_SHOW)
				break;
			int pnum = jj +1;
			page_nav += "<a href='local.jsp?" + this.constructQueryWithoutParameter(kvpairs,"startIndex") + "&startIndex=" + start_index + "'>" + pnum + "</a>&nbsp;&nbsp;";
			start_index += 10;
		}

		
        for (Entry e : lst_entry)
        {
        	
			String slink = "";
			if (e.getAttributeValue(Entry.LINK).size()>0)
				slink = (String)e.getAttributeValue(Entry.LINK).get(0);
			
			String stitle = "";
			if (e.getAttributeValue(Entry.TITLE).size()>0)
				stitle = (String)e.getAttributeValue(Entry.TITLE).get(0);
			
			String simage_link = "";
			if (e.getAttributeValue(Entry.IMAGE_LINK).size()>0)
				simage_link = (String)e.getAttributeValue(Entry.IMAGE_LINK).get(0);
			
			String sprice = "";
			if (e.getAttributeValue("s:price").size()>0)
				sprice = (String)e.getAttributeValue("s:price").get(0);
			
			Vector<String[]> v_attributes = (Vector<String[]>)e.getAttributeVector();
		
		//create a random div id number for the show/hide
		SecureRandom random = new SecureRandom();
		String div_id =  new BigInteger(130, random).toString(32);

		// first display the image, then each attribute inside a hidden div
		String strout =  "<img height=100 width=100 src='"+simage_link+"'/>"+"<br/><a href='"+slink+"'>"+stitle+"</a><br/> price: " + (String)e.getAttributeValue(Entry.PROVIDER_ID).get(0) + "<br/>";

		strout += "<div id='" + div_id + "' style=\"display:none\">";
		strout += "<table width='100%'>";
		
		for (String[] kv : v_attributes)
			strout += "<tr><td>[" + kv[0] + " (" + kv[1] + ") = "  + kv[2] + "]</td></tr>";

		strout += "</table>";
		    if (local_enabled)
		    {		
		    	String stores_to_display = "[";
				for (Inventory inv : e.al_inventories)
					if (inv.channel.equals("local"))
						for (LocalStore ls : al_stores)
							if (inv.storeId.equals(ls.storeId))
								stores_to_display += "['" + ls.lat + "','" + ls.lng + "','" + ls.name.replace("'","\\") + "','" + ls.storeId + "','" + ls.address + "'],";
				stores_to_display += "]";
			    strout += "<p><b>Local Availability:</b> <br/><div id='loc_" + div_id + "' style='height: 400px; width:400px;' ></div></p></div><p><button type='button' onclick=\"showhide('" + div_id + "','" + e.getAttributeValue(Entry.PROVIDER_ID).get(0) + "');displayMap('loc_" + div_id +"'," + stores_to_display + ");\"'>Show/Hide detail</button></p><br/>";
		    }
		    else			
				strout += "</div><p><button type='button' onclick=\"showhide('" + div_id + "','" + e.getAttributeValue(Entry.PROVIDER_ID).get(0) + "');\">Show/Hide detail</button></p><br/>";
			html_results += strout + "<hr>";
	        }
 
		
		    // now to display the facets
	        for (Facet f : lst_facet)
	        {
	        	// create the query parameter without the restricts
	        	String qparams = this.constructQueryWithoutParameter(kvpairs,"restrictBy");
				
	        	// now decode it 
	        	// yeah, i could clean it up by combining the variables somehow
	        	// ...but this is just a demo
				String srestrict = request.getParameter("restrictBy");
				if (srestrict == null)
					srestrict = "";
				else
					srestrict = decode(coded_restrict);

				// create a restrict that is based on all the previous restricts
				// provided BUT it excludes f.getName()
				String s = constructRestrct(this.getRestricts(srestrict),f.getName());
				// add on the delimiter since wi'll use this later
				if (s.length()>0)
					s += "^";
				
				// check if the its not a property (its an attribute)
	        	if (!f.is_property)
	        	{
	        		// display the facet name as an attribute
		        	html_attributes  += "<font color='blue'>" + f.getName() + " (" + f.getPrimitive() + ")"  + ": [" + f.count + "] </font><br/>";
		        	// even if its an attribute, it could be a histogram
		        	if (f.is_histogram)
		        	{
						// for each histogram bucket, generate a new restrict
						// in the form restrictBy=key(type)=(1,2]      		
						for (int i=0; i< f.getBucketCount(); i++)
						{
							HashMap hm_row = f.getRowMap(i);
							String min = (String)hm_row.get("min");
							String max = (String)hm_row.get("max");
							String minExclusive = (String)hm_row.get("minExclusive");					
							String maxExclusive = (String)hm_row.get("maxExclusive");
							String count = (String)hm_row.get("count");
							if (minExclusive == null && min == null)
							{
								minExclusive = "false";
								min = "0";
							}
							if (maxExclusive == null && max == null)
							{
								maxExclusive = "false";
								max = "*";
							}

							if (minExclusive.equals("true"))
								minExclusive = "(";
							else
								minExclusive = "[";
			
							if (maxExclusive.equals("true"))
								maxExclusive = ")";
							else
								maxExclusive = "]";
							
							String range;
			
							if (max.equals(""))
								range = "<";
							else
								range = ",";
									
							// now, even if it a histogram, the attribute
							// may not have units (its just a number)
							// if its just a number, you don't need to add on
							// it can be in key(type)=value
							// if it  does have  units its key(type)/unit=value
							
							// now the variable "restrict" is real important
							// one that is used
							// escaped_restrict is just ignored entirely and
							// there to show the restrict that is in the URL.
							
							if (f.unit != null)
							{
								String restrict = encode((s  +  f.getName() + "(" + f.getPrimitive() + ")/" + f.getUnit() + "=" + minExclusive + min +  range + max + maxExclusive));
								String escaped_restrict = 	(s  +  f.getName() + "(" + f.getPrimitive() + ")/" + f.getUnit() + "=" + minExclusive + min +  range + max + maxExclusive).replace("&","^").replace("'","%27");
								html_attributes = html_attributes + "&nbsp;&nbsp;<a href='local.jsp?" + qparams + "&raw_restrictBy=" + escaped_restrict + "&restrictBy=" + restrict   + "'>"  +  f.getName() + "(" + f.getPrimitive() + ")/" + f.getUnit() + "=" + minExclusive + min +  range + max + maxExclusive + "  (" + count  +")" + "</a><br/>";							
							}
							else
							{
								String restrict = encode((s  +  f.getName() + "(" + f.getPrimitive() + ")" + "=" + minExclusive + min +  range + max + maxExclusive)) ;
								String escaped_restrict = 	(s  +  f.getName() + "(" + f.getPrimitive() + ")" + "=" + minExclusive + min +  range + max + maxExclusive).replace("&","^").replace("'","%27");								
								html_attributes = html_attributes + "&nbsp;&nbsp;<a href='local.jsp?" + qparams + "&raw_restrictBy=" + escaped_restrict + "&restrictBy=" + restrict +  "'>"  +  f.getName() + "(" + f.getPrimitive() + ")" + "=" + minExclusive + min +  range + max + maxExclusive + "  (" + count  +")" + "</a><br/>";														
		        			}
						}
		        	}
		        	else
		        	{
		        		// now for non-histogramed attributes..just display
		        		// it in the form restrictBy=key(type)=value
		        		
						for (int i=0; i< f.getBucketCount(); i++)
						{
							HashMap hm_row = f.getRowMap(i);
							String value = (String)hm_row.get("value");
							String count = (String)hm_row.get("count");	
							String restrict = encode((s  + f.getName() +  "(" + f.getPrimitive() + ")" + "=" + value));
							String escaped_restrict = 	(s  + f.getName() +  "(" + f.getPrimitive() + ")" + "=" + value).replace("&","^").replace("'","%27");	
							html_attributes = html_attributes + "&nbsp;&nbsp;<a href='local.jsp?" + qparams +  "&raw_restrictBy=" + escaped_restrict + "&restrictBy=" + restrict + "'>"  + value + "  (" + count + ")" + "</a><br/>";
						}
		        	}
	        	}
	        	else
	        	{
	        		// for properties, encode:  restrictBy=name=value
		        	html_attributes  += "<font color='green'>" + f.getName() +  ": [" + f.count + "] </font><br/>";	 
		        	if (f.is_histogram)
		        	{
						for (int i=0; i< f.getBucketCount(); i++)
						{
							HashMap hm_row = f.getRowMap(i);
							String min = (String)hm_row.get("min");
							String max = (String)hm_row.get("max");
							String minExclusive = (String)hm_row.get("minExclusive");					
							String maxExclusive = (String)hm_row.get("maxExclusive");
							String count = (String)hm_row.get("count");
							if (minExclusive == null && min == null)
							{
								minExclusive = "false";
								min = "0";
							}
							if (maxExclusive == null && max == null)
							{
								maxExclusive = "false";
								max = "*";
							}

							if (minExclusive.equals("true"))
								minExclusive = "(";
							else
								minExclusive = "[";
			
							if (maxExclusive.equals("true"))
								maxExclusive = ")";
							else
								maxExclusive = "]";
							
							String range;
			
							if (max.equals(""))
								range = "<";
							else
								range = ",";
							String restrict = encode((s  +  f.getName() + "=" + minExclusive + min +  range + max + maxExclusive));
							String escaped_restrict = 	(s  +  f.getName() + "=" + minExclusive + min +  range + max + maxExclusive).replace("&","^").replace("'","%27");	
							html_attributes = html_attributes + "&nbsp;&nbsp;<a href='local.jsp?" + qparams + "&raw_restrictBy=" + escaped_restrict + "&restrictBy=" +  restrict   +  "'>"  +  f.getName() +  "=" + minExclusive + min +  range + max + maxExclusive + "  (" + count  +")" + "</a><br/>";														
						}
		        	}
		        	else
		        	{
						for (int i=0; i< f.getBucketCount(); i++)
						{
							HashMap hm_row = f.getRowMap(i);
							String value = (String)hm_row.get("value");
							String count = (String)hm_row.get("count");
							String restrict = encode((s  + f.getName() +  "=" + value));
							String escaped_restrict = 	(s  + f.getName() +  "=" + value).replace("&","^").replace("'","%27");							
							html_attributes = html_attributes + "&nbsp;&nbsp;<a href='local.jsp?" + qparams +  "&raw_restrictBy=" + escaped_restrict +"&restrictBy=" + restrict  + "'>"  + value + "  (" + count + ")" + "</a><br/>";
						}
		        	}

	        	}
	        }
	        	        
%>

<body class="two-column docs" onload="initialize();sendRequestID('<%=requestID%>');">

    <div id="gc-wrapper">
    <div id="gc-main">
    <div id="gc-content">
    
<center>
	<img src="http://www.google.com/intl/en/images/logos/commerce_search_logo.gif" alt="Google Commerce Search"/>
    <form id="searchForm" method="GET">
	    <table>    
    		<tr><td>search:</td><td><input id="q" name="q" type="text" size="45%" autocomplete="off" /></td></tr>     
	    <%
	    	if (local_enabled)
	    	{
				String prev_location = "";
				if (request.getParameter("location") != null)
					prev_location = URLDecoder.decode(new String(request.getParameter("location").getBytes("ISO-8859-1"), "UTF-8"),"UTF-8");
	    		out.write("<tr><td>Location: (eg. san+francisco):</td><td><input id='location' name='location' type='text' size='15%' value='" + prev_location + "'/></td></tr>");
	    		out.write("<tr><td>Autodetected location: lat (eg. +37.228369):</td><td><input id='lat' name='lat' type='text' size='10%'/>&nbsp&nbsp&nbsp;lng (eg. -122.404014):<input id='lng' name='lng' type='text' size='10%'/></td></tr>");	    		
	    		out.write("<tr><td>raidus (eg. 20):</td><td><input id='raidus' name='raidus' type='text' size='15%'/><input type='radio' name='dunit' value='mi' checked>mi &nbsp<input type='radio' name='dunit' value='km'>km</td></tr>");	    		
	    	}
	    %>	    		
		</table>	    
	    
		<input type="submit" value="Submit" "/>	
    </form>	
    
	<script type="text/javascript">
	
		var searchText = document.getElementById('q');
		
		var cx = '<%=cx%>';
	    var key = '<%=devkey%>';
		
	    google.load('search', '1');

	    var params = {
	      country: '<%=country%>',
	      language: '<%=language%>',
	      extra_params: {
	        safe: false
	      }
	    };

	    sayt = saytInitialize(cx, key, params);
	    
	    var style_options = {
	      xAlign: 'left'
	    };  

	    var opt_options = {
	      saytActor: sayt.saytSubmit,
	      styleOptions: style_options
	    };

	    google.setOnLoadCallback(function() {
	      google.search.CustomSearchControl.attachAutoCompletionWithOptions(
	        cx, searchText, 'searchForm', opt_options);
	    });
		</script>
</center>

<div style="border: 0px solid rgb(0, 0, 0); width: 80%;  overflow: auto;"  id="filters" name="filters">Filters: <%=remove_filters%></div>
<br/>
<td>Total Results:  <%=totalResults%></td>
<script src="http://www.google.com/jsapi" type="text/javascript"></script><td>Page:  <%=page_nav%></td>

<hr/>
		<table width="100%" border=2>
		<tr align="top" >

		  <td width="25%" id="attributes" name="attributes"  valign="top"><b><u>Attributes</u></b><br/><%=html_attributes%></td>

		  <td width="75%" id="results" name="results"  valign="top" ><b><u>Results</u></b><br/><%=html_results%></td>
		</tr>
		</table>

<font size="1px">
	<li><font color="blue">ShoppingAPI: </font> <a href='<%=outbound_shopping%>&prettyprint=true'><%=URLDecoder.decode(outbound_shopping,"UTF-8")%></a></li>
	<li>RequestId: <%=requestID%></li>
	<li><font color="red">Time taken: <%=time_taken%> ms</font></li>
</font>
</div>
</div>
</div>
</body>
</html>

<%!


// function to create a restrictBy string which omit a givne restrict.
// vrestricts:  a vector of all the restricts sent in to the page
// attribute:  the attribut to omit from the constructed restrict
// returns: the restrictBy= string:
// example given vrestricts that contains key1=value1  key2=value2 key3=value3
// if the attribute parameter is 'key2' then this function returns:
//  key1=value1^key3=value3

private String constructRestrct(Vector<String> vrestricts, String attribute)
{
	String restrict_string = "";
    if (((String)vrestricts.get(0)).equals(""))
	   return "";
		for (String  s : vrestricts)
		{ 	
				if (s.split("=").length>0)
					if (!s.split("=")[0].equalsIgnoreCase(attribute))
						restrict_string = restrict_string + s.split("=")[0] + "=" + s.split("=")[1]  + "^";
		}
	if (restrict_string.length()>0)
		return restrict_string.substring(0, restrict_string.length()-1);
	return restrict_string;
}

// returns a vector containing all the parsed individual restricts delimited by
// a ^   for example, givne filter_restrict=key1=value1^key2=value2^key3=value3
// returns a vector containing key1=value1   key2=value2  key3=value3

private Vector getRestricts(String filter_restricts)
{
	 		Vector v = new Vector();
			String[] fltrs = filter_restricts.split("\\^");
			for (int i=0; i< fltrs.length; i++)
			{
				String curr = fltrs[i];
				v.add(curr);	
			}
	return v;
}
 

// constructs a full query string given key-value but omits one of the parameters.
// eg. if kvpairs contains key1,value1  key2,value2 key3,value3 key4,value4
// and param_to_omit is key2,value2  the result is
// key1=value1&key3=value3&key4=value4

 private String constructQueryWithoutParameter(HashMap<String,String> kvpairs, String param_to_omit)
 {
	String ret_qstr = "";	
	Iterator ji = kvpairs.keySet().iterator();			
	try
	{
		while ( ji.hasNext() )
		{
			String key = (String) ji.next();
			String value = (String) kvpairs.get( key );
			if (!key.equalsIgnoreCase(param_to_omit) && !key.equals("raw_restrictBy"))
				if (key.equals("q") || key.equals("location"))
					ret_qstr+= key + "=" + new String(value.getBytes("ISO-8859-1"), "UTF-8") + "&"; 
				else					
					ret_qstr+= key + "=" + value.replace("+","%2B") + "&";
		}
	}
	catch (Exception ex)
	{
		System.out.println("Error: " + ex);
	}
	return ret_qstr;
 }

// object holding price inventory data

final class Inventory
{	
    public String channel = "";
    public String availability = "";
    
    public String currency = "";
    public String price = "";
    
    public String storeId = "";
    public String units = "";
    public String distance = "";
    
	public Inventory(String channel, String availability, String currency, String price) 
	{
		this.channel = channel;
		this.availability = availability;
		this.currency = currency;
		this.price = price;
	}	
    
	public Inventory(String channel, String availability, String currency, String price, String storeId, String units, String distance) 
	{
		this.channel = channel;
		this.availability = availability;
		this.currency = currency;
		this.price = price;
		this.storeId = storeId;
		this.units = units;
		this.distance = distance;
	}	    
	
}

//object holding an actual item 

final class Entry
{
    public final static String GOOGLE_ID = "s:googleId";
    public final static String PROVIDER_ID = "s:providedId";
    public final static String TITLE = "s:title";
    public final static String LINK = "s:link";
    public final static String DESC = "s:description";
    public final static String PRICE = "s:price";
    public final static String IMAGE_LINK = "s:images/@link";
    
    public final static String TYPE_TEXT = "text";
    public final static String TYPE_FLOAT = "float";
    public final static String TYPE_INT = "int";
    
    Vector<String[]> v_entry_attributes = new Vector<String[]>();
    ArrayList<Inventory> al_inventories = new ArrayList<Inventory>();
    
    public Entry() {}
    
    public void addInventory(Inventory in)
    {
    	al_inventories.add(in);
    }
   
    public ArrayList<Inventory> getInventoryList()
    {
    	return al_inventories;
    }
    
    public void addAttribute(String name, String type, String value)
    {
    	String a[] = {name,type,value};
    	v_entry_attributes.add(a);
    }
    

    public String getAttributeType(String name)
    {
		for (String[] s :v_entry_attributes)
			if (s[0].equals(name))
				return s[1];
        return null;
    }

    public Vector<String> getAttributeValue(String key)
    {
    	Vector<String> ret_values = new Vector<String>();
		for (String[] s :v_entry_attributes)
			if (s[0].equals(key))
				ret_values.add(s[2]);
		return ret_values;
    }

    public Vector<String[]> getAttributeVector()
    {
        return v_entry_attributes;
    }

}


// checks to see if a given facet name is a property or attribute
//http://code.google.com/apis/shopping/search/v1/reference-request-parameters.html#identifiers

final boolean IsProperty(String c_name)
{
	return (c_name.equalsIgnoreCase("accountId") || c_name.equalsIgnoreCase("price") ||
			c_name.equalsIgnoreCase("category") || c_name.equalsIgnoreCase("brand") ||
			c_name.equalsIgnoreCase("description") || c_name.equalsIgnoreCase("condition") ||
			c_name.equalsIgnoreCase("link") || c_name.equalsIgnoreCase("price") ||
			c_name.equalsIgnoreCase("title") );
}

// base64 encoder
final String encode(String c_name)
{ 
	byte[] encoded = Base64.encodeBase64(c_name.getBytes());
	return new String(encoded).replace("+","%2B");
	//return new sun.misc.BASE64Encoder().encode(c_name.getBytes()).toString();
}

final String decode(String c_name)
{
	byte[] decoded = Base64.decodeBase64(c_name.getBytes());
	return new String(decoded);
		//return new String(new sun.misc.BASE64Decoder().decodeBuffer(c_name)).toString();
}

final class LocalStore
{
	public String storeId;
	public String storecode;
	public String name;
	private String raw_location;
	public String address;
	public String telephone;
	public String lat;
	public String lng;
	
	public LocalStore(String storeId, String storecode, String name, String raw_location, String address, String telephone) 
    {
    	this.storeId = storeId;
    	this.storecode = storecode;
    	this.name = name;
    	this.raw_location = raw_location;
    	this.address = address;
    	this.telephone = telephone;
				
		String sloc = raw_location.substring(1,raw_location.length()-1);
		sloc = sloc.replaceAll("\\+",",+");
		sloc = sloc.replaceAll("\\-",",-");
		for (String s : sloc.split(","))
			if (s.length()>0)
		 		if (lat == null)
		 			this.lat = s;
		 		else
		 			this.lng = s;    	    	
    }
}

// function that escapes ShoppingAPI specific values:
// http://code.google.com/apis/commercesearch/docs/troubleshooting.html#5

final String escapeAttribute(String key, String aa)
{
	// if the bucket is a histogram, don't escape it since we need
	// the special characters
	
	if (key.contains("(int)") || key.contains("(float)") || 
			key.contains("raw_restrictBy") || this.IsProperty(key))
		return aa;
	
	aa = aa.replaceAll("\\\\", "\\\\\\\\");
	aa = aa.replaceAll("\\(", "\\\\(");
	aa = aa.replaceAll("\\)", "\\\\)");
	aa = aa.replaceAll("\\/", "\\\\/");
	aa = aa.replaceAll("\\=", "\\\\=");
	aa = aa.replaceAll("\\:", "\\\\:");
	aa = aa.replaceAll("\\|", "\\\\|");
	aa = aa.replaceAll("\\,", "\\\\,");
	aa = aa.replaceAll("\\~", "\\\\~");
	//aa = aa.replaceAll("'","\\&#039;");
	return aa;
}

final class Facet
{
    private String name;
    private String primitive;
    private String count;
    private String unit = null;
    public boolean is_property = false;
    public boolean is_histogram = false;

    private ArrayList<HashMap> lst_ranges = new ArrayList<HashMap>();

    // <s:facet displayName="price" name="price" type="float" unit="usd" count="10"/>
    public Facet(String facet_name, String primitive, String count, String unit) 
    {
        this.name = facet_name;
        this.primitive = primitive;
        this.count = count;
		this.unit = unit;
    }

    public void addHistogramBucket(String min, String max, String minExclusive, String maxExclusive, String count) 
    {
	HashMap hm_histo = new HashMap();
	hm_histo.put("min",min);
	hm_histo.put("max",max);
	hm_histo.put("minExclusive",minExclusive);
	hm_histo.put("maxExclusive",maxExclusive);
	hm_histo.put("count",count);
	is_histogram = true;
	lst_ranges.add(hm_histo);
    }

    public void addDiscreteBucket(String name, String count) 
    {
	HashMap hm_discrete = new HashMap();
	hm_discrete.put("value",name);
	hm_discrete.put("count",count);
	lst_ranges.add(hm_discrete);
    }

   public int getBucketCount()
   {
	return lst_ranges.size();
   }

   public HashMap getRowMap(int index)
   {
	return (HashMap)lst_ranges.get(index);
   }
   
    public String getPrimitive()
    {
	return primitive;
    }

    public String getUnit()
    {
 		return unit;
    }
	
    public String getName()
    {
	return name;
    }
}
%>